<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div class="winMineWrapper">
        <div class="winMine_link">
            <span onclick="start(8,8,10)">简单</span>
            <span onclick="start(16,16,40)">困难</span>
            <span onclick="start(16,30,99)">专家</span>
            <span onclick="start(30,70,333)">炼狱</span>
        </div>
        <div class="winMine_header">
            <div class="winMine_num"></div>
            <div class="winMine_re" onclick="start()">R</div>
            <div class="winMine_time"></div>
        </div>
    </div>
</body>
</html>
<script>
    const winMineWrapper = document.querySelector('.winMineWrapper');
    let mapState = {}
    let map = null;
    const defaultData = {
        // 雷数量
        mineNum: 99,
        // 行数量
        rowNum: 16,
        // 列数量
        colNum: 30,
        // 标记旗子数量
        flagNum: 0,
        // 问号数量
        doubtNum: 0,
        // 已使用时间
        pastTime: 0,
        // 定时器
        time: null,
        // 游戏状态
        gameState: 0, // 0: 未开始 1：进行中  3：游戏结束
        // 已清除数量
        clearNum: 0
    }
    const data = new Proxy(defaultData, {
        get: (target, attr) => {
            return target[attr]
        },
        set:(target, attr, value) => {
            if(attr === 'gameState') {
                if(data.pastTime === 0 && value === 1) setTimer();
                if(value === 3 || value === 0) clearTimeout(data.time)
            }
            target[attr] = value;
            if(attr === 'flagNum') {
                updateFlag()
            }
            if(attr === 'pastTime') {
                updatePastTime()
            }
            if(attr === 'clearNum' && value === data.colNum * data.rowNum - data.mineNum) {
                handleGameWin()
            }
        }
    })

    function start(rowNum = data.rowNum, colNum = data.colNum, mineNum = data.mineNum) {
        data.rowNum = rowNum
        data.colNum = colNum
        data.mineNum = mineNum
        data.flagNum = 0
        data.doubtNum = 0
        data.pastTime = 0
        data.clearNum = 0
        data.gameState = 0
        map = getDyadicArray(data.rowNum,data.colNum)
        // 布雷
        insertMine();
        // updateFlag();
        // updatePastTime();
        // // 生成棋盘
        createMap(map)
    }
    loadingImage();

    function updatePastTime() {
        let dom = document.querySelector('.winMine_time')
        let num = String(data.pastTime).padStart(3, '0').split('')
        dom.style.backgroundImage = `url("./images/d${num[0]}.gif"),url("./images/d${num[1]}.gif"),url("./images/d${num[2]}.gif")`
    }
    // 获取公共数据
    function getCommonData(index) {
        return {
            mark: 0, // -1：雷
            state: 0, // 1：标记 2：可疑 3: 排除
            index
        }
    }
    // 预加载所需背景图
    function loadingImage() {
        for (let i = 0; i < 10; i++) {
            let img = new Image()
            img.src = `./images/d${i}.gif`
        }
    }
    // 更新标记旗子数量dom
    function updateFlag() {
        let dom = document.querySelector('.winMine_num')
        let num = String(data.mineNum - data.flagNum).padStart(3, '0').split('')
        dom.style.backgroundImage = `url("./images/d${num[0]}.gif"),url("./images/d${num[1]}.gif"),url("./images/d${num[2]}.gif")`
    }
    // 更新标记已用时间dom
    // 生成棋盘 [9 * 9] 创建二维数组
    function getDyadicArray(rowLength, colLength) {
        mapState = {}
        // return new Array(rowLength).fill(new Array(colLength).fill(getCommonData()))
        return new Array(rowLength).fill('').map((m, rowIndex) => {
            return new Array(colLength).fill('').map((m, colIndex) => {
                const mapIndex = `${rowIndex}-${colIndex}`
                let commonData = getCommonData(mapIndex)
                mapState[mapIndex] = commonData
                return commonData
            })
        })
    }
    // 创建棋盘
    function createMap(arr) {
        let winMineGame = document.createElement('div')
        winMineGame.classList.value = 'winMineGame'
        arr.forEach((f, rowIndex) => {
            const tr = document.createElement('div')
            tr.classList.add('winMine_row')
            winMineGame.appendChild(tr)
            f.forEach((c, colIndex) => {
                const td = document.createElement('div')
                td.dataset.index = `${rowIndex}-${colIndex}`
                c.mark !== -1 && (c.mark = getMineNum(`${rowIndex}-${colIndex}`))
                td.classList.add('winMine_td')
                regEvent(td)
                tr.appendChild(td)
            })
        })
        let dom = document.querySelector('.winMineGame')
        dom && winMineWrapper.removeChild(dom)
        winMineWrapper.appendChild(winMineGame)
    }
    // 随机布雷
    function insertMine() {
        Object.keys(mapState).sort(() => Math.random() - 0.5).slice(0, data.mineNum).forEach(f => {
            mapState[f].mark = -1
        })
    }
    // 获取周围雷的数量
    function getMineNum(i) {
        let indexList = getAreaXY(i);
        return indexList.reduce((a,b)  => {
            return a + (mapState[b].mark === -1 ? 1 : 0)
        }, 0)
    }
    // 获取周围标记的数量
    function getFlagNumByXY(i) {
        let indexList = getAreaXY(i);
        return indexList.reduce((a,b)  => {
            return a + ((mapState[b].state === 1 || mapState[b].state === 2) ? 1 : 0)
        }, 0)
    }
    // 获取标记的数量
    function getFlagNum() {
        return Object.keys(mapState).reduce((a,b)  => {
            return a + (mapState[b].state === 1 ? 1 : 0)
        }, 0)
    }
    // 时间++
    function setTimer() {
        // setTimeout()
        data.time = setTimeout(() => {
            data.pastTime++;
            setTimer();
        }, 1000)
    }
    // 游戏结束
    function handleGameOver() {
        data.gameState = 3;
        alert('GAME OVER！');
    }
    // 游戏胜利
    function handleGameWin() {
        data.gameState = 3;
        alert(`Win！用时${data.pastTime}秒`);
    }
    // 注册交互事件
    function regEvent(dom) {
        dom.onclick = function (e) {
            if(data.gameState === 3) return;
            let { target } = e
            let xy = target.dataset.index

            let info = mapState[xy];
            if(data.gameState === 0) data.gameState = 1;
            // 计时开始
            if(info.state === 1 || info.state === 2) return;
            // 判断游戏结束
            if(info.mark === -1) return handleGameOver()

            // 已排雷
            if(info.state === 3) {
                let indexList = getAreaXY(xy)
                // 周围标记 等于 周围雷的数量
                if(getFlagNumByXY(xy) === info.mark) {
                    indexList.forEach(exy => {
                        forEachByXY(exy)
                    })
                } else {
                    let state0 = indexList.filter(f => mapState[f].state === 0);
                    state0.forEach(item => {
                        // setStateByXY(item, 0)
                        getDomByXY(item).classList.value = `winMine_td mark0`
                        setTimeout(() => {
                            getDomByXY(item).classList.value = `winMine_td`
                        }, 100)
                    })
                }
            }

            forEachByXY(target.dataset.index)
        }
        dom.oncontextmenu = function (e) {
            e.preventDefault();
            if(data.gameState === 3) return;
            let { target } = e
            let xy = target.dataset.index

            let info = mapState[xy];

            if(info.state === 3) return;
            setStateByXY(xy, info.state+1 > 2 ? 0 : info.state+1)
            // getFlagNum()
            // console.log(getFlagNum())
        }
    }
    // 根据坐标获取dom
    function getDomByXY(xy) {
        return document.querySelector(`[data-index='${xy}']`)
    }
    // 递归排雷
    function forEachByXY(xy) {
        if(data.gameState === 3) return;
        // 已排除
        if(mapState[xy].state !== 0) return;
        // 雷
        if(mapState[xy].mark === -1) return handleGameOver();
        setStateByXY(xy, 3)
        if(mapState[xy].mark === 0) {
            let indexList = getAreaXY(xy).filter(f => mapState[f].state === 0);

            indexList.forEach(index => {
                if(data.gameState === 3) return;
                if(mapState[index].mark === 0) return forEachByXY(index);
                setStateByXY(index, 3)
            })
        }
    }
    // 设置坐标状态
    function setStateByXY(xy, state) {
        // 排除
        if(state === 3) {
            if(mapState[xy].state === 3) return
            data.clearNum++
            getDomByXY(xy).classList.value = `winMine_td mark${mapState[xy].mark}`
        // 标记为雷
        }else if(state === 1) {
            data.flagNum++
            getDomByXY(xy).classList.value = `winMine_td mine-1`
            // 标记为问号
        }else if(state === 2) {
            data.flagNum--
            data.doubtNum++
            getDomByXY(xy).classList.value = `winMine_td mine-2`
            // 默认状态
        } else {
            data.doubtNum--
            getDomByXY(xy).classList.value = `winMine_td`
        }
        mapState[xy].state = state
    }
    //返回周围坐标
    function getAreaXY(xy) {
        let [rowIndex,colIndex] = xy.split('-')
        let indexList = []
        for (let x = rowIndex*1 - 1; x <= rowIndex*1 + 1; x++) {
            for (let y = colIndex*1 - 1; y <= colIndex*1 + 1; y++) {
                let str = `${x}-${y}`
                x >= 0 && x < data.rowNum && y >=0 && y < data.colNum && str !== xy &&  indexList.push(str)
            }
        }
        return indexList
    }
</script>
<style>
    .winMineWrapper{
        margin: 0 auto;
        width: fit-content;
        min-width: 200px;
    }
    .winMine_link span{
        cursor: pointer;
        color: #25acf5;
        text-decoration: underline;
        margin-right: 10px;
        text-align: left;
    }
    .winMine_header{
        width: 100%;
        height: 30px;
        border: 1px solid silver;
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
    .winMine_header .winMine_num, .winMine_header .winMine_time{
        width: 39px;
        height: 23px;
        background-color: #000;
        background-repeat: no-repeat;
        background-position: 2px,center,calc(100% - 2px);
        padding: 2px;
    }
    .winMine_re{
        cursor: pointer;
    }
    .winMineGame{
        margin: 0 auto;
    }
    .winMine_row{
        white-space: nowrap;
    }
    .winMine_td{
        display: inline-block;
        width: 25px;
        height: 25px;
        /*background-color: red;*/
        /*border: 1px solid #fff;*/
        background: url("./images/blank.gif") no-repeat;
        background-size: 100% 100%;
    }
    .mark0{
        background: url("./images/0.gif") no-repeat;background-size: 100% 100%;
    }
    .mark1{
        background: url("./images/1.gif") no-repeat;background-size: 100% 100%;
    }
    .mark2{
        background: url("./images/2.gif") no-repeat;background-size: 100% 100%;
    }
    .mark3{
        background: url("./images/3.gif") no-repeat;background-size: 100% 100%;
    }
    .mark4{
        background: url("./images/4.gif") no-repeat;background-size: 100% 100%;
    }
    .mark5{
        background: url("./images/5.gif") no-repeat;background-size: 100% 100%;
    }
    .mark6{
        background: url("./images/6.gif") no-repeat;background-size: 100% 100%;
    }
    .mark7{
        background: url("./images/7.gif") no-repeat;background-size: 100% 100%;
    }
    .mark8{
        background: url("./images/8.gif") no-repeat;background-size: 100% 100%;
    }
    .mine-1{
        background: url("./images/flag.gif") no-repeat;background-size: 100% 100%;
    }
    .mine-2{
        background: url("./images/ask.gif") no-repeat;background-size: 100% 100%;
    }
</style>